///:SqlSessionImpl.java
package cn.weyoungstudio.hwes.jdbc.session;

import cn.weyoungstudio.hwes.exception.DataAccessException;
import cn.weyoungstudio.hwes.jdbc.common.SQLParameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author icechen1219
 * @date 2020/05/22
 */
public class SqlSessionImpl implements SqlSession {
    private static final Logger log = LoggerFactory.getLogger(SqlSessionImpl.class);
    private Transaction transaction;

    public SqlSessionImpl(DataSource dataSource, boolean autoCommit) {
        this.transaction = new JdbcTransaction(dataSource, autoCommit);
    }

    @Override
    public Transaction openTransaction() {
        return this.transaction;
    }

    @Override
    public int executeUpdate(String sql, List<SQLParameter> params) throws SQLException {
        Connection connection = transaction.getConnection();

        int rows = 0;
        try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
            setParams(preparedStatement, params);
            guessPreparedSql(sql, params);

            rows = preparedStatement.executeUpdate();
            transaction.commit();
        } catch (SQLException e) {
            transaction.rollback();
            throw new DataAccessException(e.getMessage(), e);
        } finally {
            transaction.close();
        }

        return rows;
    }

    @Override
    public Integer executeUpdate(String sql, List<SQLParameter> params, boolean needKeys) throws SQLException {
        if (needKeys) {
            Connection connection = transaction.getConnection();
            Integer primaryKey = null;
            try (PreparedStatement preparedStatement = connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS)) {
                setParams(preparedStatement, params);
                guessPreparedSql(sql, params);

                preparedStatement.executeUpdate();
                transaction.commit();
                ResultSet generatedKeys = preparedStatement.getGeneratedKeys();

                if (generatedKeys.next()) {
                    primaryKey = generatedKeys.getInt(1);
                }

            } catch (SQLException e) {
                transaction.rollback();
                throw new DataAccessException(e.getMessage(), e);
            } finally {
                transaction.close();
            }
            return primaryKey;
        } else {
            return executeUpdate(sql, params);
        }
    }

    @Override
    public List<Map<String, Object>> find(String sql, List<SQLParameter> params) throws SQLException {
        Connection connection = transaction.getConnection();
        List<Map<String, Object>> list = new ArrayList<>();
        ResultSet resultSet = null;

        try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
            setParams(preparedStatement, params);
            guessPreparedSql(sql, params);

            resultSet = preparedStatement.executeQuery();

            while (resultSet.next()) {
                Map<String, Object> map = new HashMap<>();
                ResultSetMetaData metaData = resultSet.getMetaData();
                int columnCount = metaData.getColumnCount();
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = metaData.getColumnName(i);
                    map.put(columnName, resultSet.getObject(columnName));
                }
                list.add(map);
            }
        } catch (SQLException e) {
            throw new DataAccessException(e.getMessage(), e);
        } finally {
            if (resultSet != null) {
                resultSet.close();
            }
        }

        return list;
    }

    @Override
    public <E> E selectOne(E obj) {
        return null;
    }

    private void setParams(PreparedStatement ps, List<SQLParameter> params) throws SQLException {
        if (params == null) {
            return;
        }

        for (int i = 0; i < params.size(); i++) {
            SQLParameter param = params.get(i);
            if (param.getTargetSqlType() == SQLParameter.AUTO_TYPE) {
                ps.setObject(i + 1, param.getValue());
            } else {
                ps.setObject(i + 1, param.getValue(), param.getTargetSqlType());
            }
        }
    }

    /**
     * 获得PreparedStatement向数据库提交的SQL语句
     *
     * @param sql
     * @param params
     * @return
     */
    private String guessPreparedSql(String sql, List<SQLParameter> params) {
        int paramNum = 0;

        // 如果参数集不为空，取得其长度
        if (null != params) {
            paramNum = params.size();
        }

        // 如果没有参数，说明不是动态SQL语句，直接返回原sql
        if (1 > paramNum) {
            log.info("prepared sql: " + sql);
            return sql;
        }

        // 如果有参数，则是动态SQL语句，需要构造并返回新sql
        StringBuffer returnSQL = new StringBuffer();
        String[] subSQL = sql.split("\\?");

        // 开始循环替换问号为参数值
        for (int i = 0; i < paramNum; i++) {
            Object value = params.get(i).getValue();
            if (value == null) {
                System.err.printf("第 %d 个参数的值为 null %n", i + 1);
                returnSQL.append(subSQL[i]).append(value);
                continue;
            }
            if (value instanceof Number) {
                // 数值不需要引号
                returnSQL.append(subSQL[i]).append(value);
            } else {
                // 非数值需要引号，遇到特殊字符将其转义输出
                String str = value.toString().replaceAll("(['\\\\])", "\\\\$1");
                returnSQL.append(subSQL[i]).append(" '").append(str).append("' ");
            }
        }

        // 如果问号不是原sql的最后一个字符，则将改问号后的部分添加到returnSQL中
        if (subSQL.length > params.size()) {
            returnSQL.append(subSQL[subSQL.length - 1]);
        }

        String formatSql = returnSQL.toString();
        log.info("prepared sql: " + formatSql);

        return formatSql;
    }
}
///:SqlSessionImpl.java
